package Server;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

import logic.PlayRequest;
import logic.User;

import communication.ActionRequest;
import communication.ClientMessageID;
import communication.ConnectRequest;
import communication.ConnectResponse;
import communication.GameMessage;
import communication.GameRequest;
import communication.ServerMessage;
import communication.ServerMessageID;

public class Player extends Thread{

	public final static int STATE_NONE = 0;
	public final static int STATE_CONNECTED = 1;
	public final static int STATE_PLAYING = 2;
	public final static int STATE_WAITING = 3;
	
	
	/*-------------------------*/
	
	
	private Socket socket;
	private Server server;
	private ObjectInputStream in = null;
	private ObjectOutputStream out = null;
	private volatile boolean stop = false;
	
	private User user = null;
	private Game game = null;
	
	private boolean loggedIn = false;
	private int state = STATE_NONE;
	

	/*-----------------------------*/
	
	public void requestStop() {
	    stop = true;
	}	
	
	public Player(Server server, Socket s) {
		this.socket = s;
		this.server = server;
		this.state = STATE_NONE;
	}
	
	/*------- getters and setters --------*/
	
	public Socket getSocket() {
		return socket;
	}
	
	public User getUser() {
		return user;
	}
	
	public void setUser(User user) {
		this.user = user;
	}
	
	public Game getGame() {
		return game;
	}
	
	public void setGame(Game game) {
		this.game = game;
	}
	
	public boolean isLoggedIn() {
		return loggedIn;
	}
	
	public ObjectInputStream getIn() {
		return in;
	}
	
	public ObjectOutputStream getOut() {
		return out;
	}
	
	public int getPlayerState() {
		return state;
	}
	
	public void setPlayerState(int state) {
		this.state = state;
	}
	
	public boolean isWaiting(){
		return state==STATE_WAITING;		
	}
	
	/*-------------------------------------------------*/
	
	@Override
	public void run() {
			
		ClientMessageID cmid;
		ConnectRequest connReq;
		GameRequest gameReq;
		ActionRequest actionReq;
		
		try{

			in = new ObjectInputStream(socket.getInputStream());
			out = new ObjectOutputStream(socket.getOutputStream());
			
			cmid = (ClientMessageID) in.readObject();
			connReq = (ConnectRequest) in.readObject();

			handleConnectRequest(connReq);

			if (!isLoggedIn()){
				disconnectClient();
				return;
			}

		} catch (ClassNotFoundException e) {
			
			System.err.println("Invalid Request Received!"); 
			e.printStackTrace();
			disconnectClient();
			return;

		} catch (IOException e) {
			
			System.err.println("Error handling client!");
			e.printStackTrace();
			disconnectClient();
			return;
		}
		
		
		
		// --- CONNECTED OK -----------------
		
		System.out.println("[Client Handler] Player '" + getUser().getUserName() + "' Connected!");
		

		//Send first update message
		sendMessage(server.getData().getUpdateMessageTo(this));
					
		// Send update message to other Players
		server.getData().sendUpdateAll();
		
		
		while(!stop){
			
			try {			
				
				cmid = (ClientMessageID) in.readObject();
				
								
				switch(cmid.getType()){
				
					case ClientMessageID.CONNECT_REQUEST:
						
						connReq = (ConnectRequest) in.readObject();
						
						//handleConnectRequest(connReq);
						
						break;
						
					case ClientMessageID.ACTION_REQUEST:
						
						actionReq = (ActionRequest) in.readObject();
						
						handleActionRequest(actionReq);
												
						break;
						
					case ClientMessageID.GAME_REQUEST:
						
						gameReq = (GameRequest) in.readObject();
						
						if (game!=null)
							game.handleGameRequest(gameReq, this);
						
						break;

				
					default:
						in.reset();
						System.err.println("Error! Invalid Request type received!");
				
				}
				
				
			}catch (EOFException ex){
				ex.printStackTrace();				
			
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
				
			} catch (IOException e) {

				this.stop = true;
				if (game!=null && state==STATE_PLAYING)
					game.quit(this);
					
				server.getData().disconnectAndUpdate(this);
				
			}
					
		}
		
	
	}
	
	
	
	/**
	 * 
	 * @param req
	 * @return boolean - indicates if the users is authorized ou not to stay connected
	 * @throws IOException
	 */
	
	private void handleConnectRequest(ConnectRequest req){
		
		 ConnectResponse response;
        
		 switch (req.getType()){
		 
			 case ConnectRequest.REQ_LOGIN:
				 
				 if (server.getData().isConnected(req.getUsername()))
					 response = new ConnectResponse(ConnectResponse.RESP_LOGIN_ALREADY_LOGGED);
				 else
					 switch(server.getData().checkLogin(req.getUsername(), req.getPassword())){
					 
					 case ServerData.LOGIN_OK:
						 
						 this.loggedIn = true;
						 this.state = STATE_CONNECTED;
						 this.setUser(server.getData().getUserByUsername(req.getUsername()));
						 
						 response = new ConnectResponse(ConnectResponse.RESP_LOGIN_OK);
	
						 break;
					 	
					 case ServerData.LOGIN_INVALID_USERNAME:
						 
						 response = new ConnectResponse(ConnectResponse.RESP_LOGIN_INVALID);
						 
						 break;
						 
					 case ServerData.LOGIN_INVALID_PASSWORD:
						 
						 response = new ConnectResponse(ConnectResponse.RESP_LOGIN_INVALID);
						 
						 break;
						
					 case ServerData.LOGIN_FULL:
						 
						 response = new ConnectResponse(ConnectResponse.RESP_LOGIN_FULL);
						 break;
						 
					default:
						 response = new ConnectResponse(ConnectResponse.RESP_INVALID_REQUEST);
					 
					 }

				 break;
			 
		
			 case ConnectRequest.REQ_REGISTER:
				 
				 
				 if (server.getData().isRegistered(req.getUsername()))
					 response = new ConnectResponse(ConnectResponse.RESP_REGISTER_USERNAME_ALREADY_EXISTS);
				 else
					 if (!server.getData().addUser(req.getUsername(), req.getPassword()))
						 response = new ConnectResponse(ConnectResponse.RESP_REGISTER_ERROR);
					 else{
						 this.loggedIn = true;
						 this.state = STATE_CONNECTED;
						 this.setUser(server.getData().getUserByUsername(req.getUsername()));
						 
						 response = new ConnectResponse(ConnectResponse.RESP_REGISTER_OK);
						 
					 }
				
	      		 
	      		 break;
	      		 
	      		 
			 default:
				 System.out.println("Invalid Request!");
	      		 response = new ConnectResponse(ConnectResponse.RESP_INVALID_REQUEST);
	      		 			 
		 }
   		 
  	    sendConnectResponse(response);
  	    
  	    if (isLoggedIn())
  	    	server.getData().login(this);
  	     	
	}
	

	
	
	/**
	 * Handles the ActionRequest
	 * 
	 * @param request - ActionRequest sent from the client
	 * @return 
	 */
	private void handleActionRequest(ActionRequest request){

		Player p;
		
		if (request!=null)
			switch(request.getType()){
			
			case ActionRequest.REQ_MESSAGE:
				
				server.getData().sendChatMsg(this, request.getMessage());
				
				break;

			case ActionRequest.REQ_PLAY_WITH:
				// User clicked in Play (with other Waiting User)
				
				synchronized (server.getData().getPlayers()) {

					p = server.getData().getPlayerByUsername(request.getUsername());

					if (p!=null)
						if (p.isWaiting()){

							startGameWith(p);
							
						}
				}

				break;
			
			case ActionRequest.REQ_WAITING:
				
				if (getPlayerState()==Player.STATE_CONNECTED){
					setPlayerState(Player.STATE_WAITING);
					server.getData().sendUpdateAll();
				}
				break;
				
			case ActionRequest.REQ_CANCEL_WAITING:
				
				if (getPlayerState()==Player.STATE_WAITING){
					setPlayerState(Player.STATE_CONNECTED);
					server.getData().sendUpdateAll();
				}					
				break;
									
			case ActionRequest.REQ_INVITE:
				
				p = server.getData().getPlayerByUsername(request.getUsername());
				if (p!=null){	
					
					PlayRequest preq = server.getData().getPlayRequest(getUser().getUserName(), request.getUsername());
					if (preq == null){
						
						server.getData().getPlayRequests().add(new PlayRequest(getUser().getUserName(), p.getUser().getUserName()));
						
						ServerMessage msg = new ServerMessage(ServerMessage.MSG_NEW_INVITATION);
						msg.setUsername(user.getUserName());
						
						p.sendMessage(msg);
						
						server.requestUpdate();
					
					}
				}
				
				break;
				
			case ActionRequest.REQ_UNINVITE:
				
				p = server.getData().getPlayerByUsername(request.getUsername());
				if (p!=null){
					
					PlayRequest preq = server.getData().getPlayRequest(getUser().getUserName(), request.getUsername());
					if (preq != null){
						
						ServerMessage msg = new ServerMessage(ServerMessage.MSG_INVITATION_CANCELED);
						msg.setUsername(user.getUserName());
						
						server.getData().getPlayRequests().remove(preq);
						p.sendMessage(msg);
												
					}
				}
				
				break;
		
			case ActionRequest.REQ_REJECT:
							
				p = server.getData().getPlayerByUsername(request.getUsername());
				if (p!=null){
					
					PlayRequest preq = server.getData().getPlayRequest(request.getUsername(), getUser().getUserName());
					if (preq != null){
						
						ServerMessage msg = new ServerMessage(ServerMessage.MSG_INVITATION_REJECTED);
						msg.setUsername(user.getUserName());
						p.sendMessage(msg);
						
						server.getData().getPlayRequests().remove(preq);
					}
				}
				
				break;
				
			case ActionRequest.REQ_LOGOUT:
				
				this.loggedIn = false;
				this.state = STATE_NONE;
				this.requestStop();
				server.getData().logout(this);
				
				
					break;
			
			case ActionRequest.REQ_ACCEPT:
				
				p = server.getData().getPlayerByUsername(request.getUsername());
				if (p!=null){
					PlayRequest preq = server.getData().getPlayRequest(request.getUsername(), getUser().getUserName());
					
					if (preq!=null){
						
						ServerMessage msg = new ServerMessage(ServerMessage.MSG_INVITATION_ACCEPTED);
						msg.setUsername(user.getUserName());
						p.sendMessage(msg);
						
						server.getData().getPlayRequests().remove(preq);
						
						startGameWith(p);
						
					}
				}
				
				break;
					
			}
		
		server.requestUpdate();
		
	}
		
	public void disconnectClient(){
		
		try {
			
			in.close();
			out.close();
			socket.close();
			
		} catch (Exception e) {
			System.err.println("Disconnect Client: " + e);
		}
		
		stop = true;
		server.getData().getPlayers().remove(this);
		
	}

	private boolean sendConnectResponse(ConnectResponse res){
		
		try {
			synchronized (out) {
				out.writeObject(new ServerMessageID(ServerMessageID.CONNECT_RESPONSE));
				out.writeObject(res);
				out.flush();
			}
			
		} catch (IOException e) {
			e.printStackTrace();
			//disconnectClient();
			return false;
		}
		
		return true;
		
	}
	
	public boolean sendGameMessage(GameMessage msg){
		
		try {
			synchronized (out) {
				out.writeObject(new ServerMessageID(ServerMessageID.GAME_MESSAGE));
				out.writeObject(msg);
				out.flush();
			}
			
		} catch (IOException e) {
			e.printStackTrace();
			//disconnectClient();
			return false;
		}
		
		return true;

	}
	
	public boolean sendMessage(ServerMessage msg){
		
		try {
			synchronized (out) {
				out.writeObject(new ServerMessageID(ServerMessageID.SERVER_MESSAGE));
				out.writeObject(msg);
				out.flush();
			}
			
		} catch (IOException e) {
			e.printStackTrace();
			//disconnectClient();
			return false;
		}
		
		return true;

	}

	private void startGameWith(Player p){
		
		ServerMessage msg = new ServerMessage(ServerMessage.MSG_PLAY_GAME);
		msg.setUsername(p.getUser().getUserName());

		sendMessage(msg);

		msg.setUsername(user.getUserName());
		p.sendMessage(msg);
		
		Game g = server.getData().createGame(this, p);

		this.setGame(g);
		p.setGame(g);
		
		this.setPlayerState(Player.STATE_PLAYING);
		p.setPlayerState(STATE_PLAYING);
		
	}


}
